package com.meeruu.sg.account.core.domain.biz;

import com.alibaba.fastjson.JSON;
import com.meeruu.boot.did.service.MeeruuDidService;
import com.meeruu.common.utils.LoggerUtil;
import com.meeruu.sg.account.core.api.constant.AccountGroup;
import com.meeruu.sg.account.core.api.constant.AccountOprType;
import com.meeruu.sg.account.core.api.constant.AccountStatus;
import com.meeruu.sg.account.core.api.constant.AccountType;
import com.meeruu.sg.account.core.api.dto.request.oper.*;
import com.meeruu.sg.account.core.api.exception.AccountException;
import com.meeruu.sg.account.core.api.exception.ErrorCode;
import com.meeruu.sg.account.core.domain.mapper.DataMoveLoggerMapper;
import com.meeruu.sg.account.core.domain.mapper.MerchantAccountMapper;
import com.meeruu.sg.account.core.domain.mapper.OrgAccountMapper;
import com.meeruu.sg.account.core.domain.mapper.UserAccountMapper;
import com.meeruu.sg.account.core.domain.model.DataMoveLog;
import com.meeruu.sg.account.core.domain.model.MerchantAccountDO;
import com.meeruu.sg.account.core.domain.model.OrgAccountDO;
import com.meeruu.sg.account.core.domain.model.UserAccountDO;
import com.meeruu.sg.account.core.domain.vo.account.AccountSortVo;
import com.meeruu.sg.account.core.domain.vo.account.BaseAccount;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author yangqintao
 * @date 2019-05-20
 * @description 用户账户增删改操作
 */
@Slf4j
@Service
public class AccountHandleBiz {

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private UserAccountMapper userAccountMapper;

    @Resource
    private MerchantAccountMapper merchantAccountMapper;

    @Resource
    private OrgAccountMapper orgAccountMapper;

    @Resource
    private DataMoveLoggerMapper dataMoveLoggerMapper;

    @Resource
    private FundsBalanceBiz fundsBalanceBiz;

    @Resource
    private AccountCacheBiz accountCacheBiz;

    @Resource
    private MeeruuDidService meeruuDidService;

    @Resource
    private Environment environment;


    /**
     * 账户开户
     */
    public String openAccount(OpenAccountRequest request) {

        if (request instanceof UserOpenAccountRequest) {
            // 用户账户开户
            UserOpenAccountRequest userRequest = (UserOpenAccountRequest) request;
            LoggerUtil.info(log, "用户开户请求开始", JSON.toJSONString(userRequest));
            UserAccountDO existUserAccount = userAccountMapper.selectByUserCodeAndType(userRequest.getUserCode(),
                    userRequest.getAccountType().getCode());
            if (null != existUserAccount) {
                LoggerUtil.error(log, ErrorCode.BIZ_ACCOUNT_EXIST.getMessage(), existUserAccount);
                throw new AccountException(ErrorCode.BIZ_ACCOUNT_EXIST);
            }

            UserAccountDO userAccount = this.openAccountForUser(userRequest);
            return userAccount.getAccountId();

        } else if (request instanceof MerchantOpenAccountRequest) {
            // 商家账户开户
            MerchantOpenAccountRequest merchantRequest = (MerchantOpenAccountRequest) request;
            LoggerUtil.info(log, "商家开户请求开始", JSON.toJSONString(merchantRequest));
            MerchantAccountDO existMerchantAccount = merchantAccountMapper.selectByMerchantCodeAndType(merchantRequest.getMerchantCode(),
                    merchantRequest.getAccountType().getCode());
            if (null != existMerchantAccount) {
                LoggerUtil.error(log, ErrorCode.BIZ_ACCOUNT_EXIST.getMessage(), existMerchantAccount);
                throw new AccountException(ErrorCode.BIZ_ACCOUNT_EXIST);
            }

            MerchantAccountDO merchantAccount = this.openAccountForMerchant(merchantRequest);
            return merchantAccount.getAccountId();

        } else if (request instanceof OrgOpenAccountRequest) {
            // 机构账户开户
            OrgOpenAccountRequest orgRequest = (OrgOpenAccountRequest) request;
            LoggerUtil.info(log, "机构开户请求开始", JSON.toJSONString(orgRequest));
            OrgAccountDO existOrgAccount = orgAccountMapper.selectByAccountName(orgRequest.getAccountName().trim());
            if (null != existOrgAccount) {
                LoggerUtil.error(log, ErrorCode.BIZ_ACCOUNT_EXIST.getMessage(), existOrgAccount);
                throw new AccountException(ErrorCode.BIZ_ACCOUNT_EXIST);
            }

            OrgAccountDO orgAccount = this.openAccountForOrg(orgRequest);
            return orgAccount.getAccountId();

        } else {
            LoggerUtil.error(log, "没有找到对应的账户请求类型%s", JSON.toJSONString(request));
            throw new AccountException(ErrorCode.BIZ_ACCOUNT_REQUEST_NOT_EXIST.getCode(), ErrorCode.BIZ_ACCOUNT_REQUEST_NOT_EXIST.getMessage());
        }
    }

    /**
     * 注销账户
     */
    public Boolean closeUserAccount(CloseUserAccountRequest request) {
        if (request == null || StringUtils.isBlank(request.getUserCode()) || request.getAccountType() == null) {
            LoggerUtil.error(log, "关闭用户账户参数错误", JSON.toJSONString(request));
            return false;
        }
        UserAccountDO userAccountDO = userAccountMapper.selectByUserCodeAndType(request.getUserCode(), request.getAccountType().getCode());
        if (userAccountDO == null) {
            LoggerUtil.info(log, "账户不存在，默认账户成功");
            return true;
        }
        int closeAccountResult = userAccountMapper.closeAccount(request.getUserCode(), request.getAccountType().getCode());
        LoggerUtil.info(log, "账关闭用户账户:", closeAccountResult);
        return closeAccountResult > 0;
    }

    public Boolean closeUserAccountBatch(List<String> userCodes, AccountType accountType) {
        if (CollectionUtils.isEmpty(userCodes) || accountType == null) {
            LoggerUtil.error(log, "关闭用户账户参数错误", JSON.toJSONString(userCodes), accountType);
            return false;
        }
        List<UserAccountDO> userAccountDOS = userAccountMapper.selectByUserCodeGroupAndType(userCodes, accountType.getCode());
        if (CollectionUtils.isEmpty(userAccountDOS)) {
            LoggerUtil.info(log, "账户不存在，默认账户成功");
            return true;
        }
        List<String> needCloseUsers = userAccountDOS.parallelStream().map(UserAccountDO::getUserCode).collect(Collectors.toList());
        return userAccountMapper.closeAccountBatch(needCloseUsers, accountType.getCode()) == needCloseUsers.size();
    }

    /**
     * @param userAccountInfos
     * @return userCode -> accountId
     */
    private Map<String, String> openAccountBatchWithAmount(List<SyncUserAccount> userAccountInfos) {
        if (CollectionUtils.isEmpty(userAccountInfos)) {
            return new HashMap<>();
        }

        List<UserAccountDO> needOpenAccountUsers = userAccountInfos.stream().map(user ->
        {
            String accountId = meeruuDidService.next18Id("U");
            return UserAccountDO.builder()
                    .accountId(accountId)
                    .accountName(accountId)
                    .accountType(user.getAccountType().getCode())
                    .userCode(user.getUserCode())
                    .describe(user.getAccountType().getDesc())
                    .accountOpenDate(new Date())
                    .status(AccountStatus.NORMAL.getCode())
                    .availableBalance(user.getAccountAmount())
                    .frozenBalance(BigDecimal.ZERO)
                    .totalBalance(user.getAccountAmount())
                    .build();
        }).collect(Collectors.toList());
        userAccountMapper.insertBatch(needOpenAccountUsers);
        return needOpenAccountUsers.stream().collect(Collectors.toMap(UserAccountDO::getUserCode, UserAccountDO::getAccountId));
    }

    /**
     * 更新机构账户信息
     *
     * @param updateOrgAccountRequest
     * @return
     */
    public boolean updateOrgAccount(UpdateOrgAccountRequest updateOrgAccountRequest) {
        OrgAccountDO orgAccountDO = orgAccountMapper.selectByAccountId(updateOrgAccountRequest.getAccountId());
        String orgAccountName = updateOrgAccountRequest.getAccountName();
        //账户名称一致时，无需更新账户名称，否则会报重复主键冲突。
        if (orgAccountDO != null && StringUtils.isNotEmpty(orgAccountName) && StringUtils.equals(orgAccountName, orgAccountDO.getAccountName())) {
            orgAccountName = null;
        }
        boolean result = false;
        try {
            result = orgAccountMapper.updateAccountInfo(updateOrgAccountRequest.getAccountId(),
                    orgAccountName, updateOrgAccountRequest.getDescribe()) > 0;
        } catch (DuplicateKeyException e) {
            LoggerUtil.warn(log, "账户名称已存在", updateOrgAccountRequest);
            throw new AccountException(ErrorCode.BIZ_ACCOUNT_NAME_EXIST);
        }
        return result;
    }

    public void syncUserAccountBatch(List<SyncUserAccount> syncUserAccounts) {
        if (CollectionUtils.isEmpty(syncUserAccounts) || syncUserAccounts.get(0) == null || syncUserAccounts.get(0).getAccountType() == null) {
            return;
        }
        String meeruuScoreAccount = environment.getProperty("meeruu.score.account");
        String meeruuShowValueAccount = environment.getProperty("meeruu.showValue.account");
        if (StringUtils.isBlank(meeruuScoreAccount) || StringUtils.isBlank(meeruuShowValueAccount)) {
            LoggerUtil.error(log, "EDAS 配置信息错误，meeruu.score.account或meeruu.showValue.account者未配置。");
            return;
        }
        transactionTemplate.execute((status) -> {

            LoggerUtil.info(log, "syncUserAccount start log");
            // 关键动作1：开户
            List<String> userCodes = syncUserAccounts.parallelStream().map(SyncUserAccount::getUserCode).collect(Collectors.toList());
            AccountType accountType = syncUserAccounts.get(0).getAccountType();
            boolean closeAccountResult = closeUserAccountBatch(userCodes, accountType);
            Map<String, String> userCodeAndAccountType = new ConcurrentHashMap<>();
            if (closeAccountResult) {
                userCodeAndAccountType.putAll(openAccountBatchWithAmount(syncUserAccounts));
            }
            if (userCodeAndAccountType.size() == 0) {
                LoggerUtil.error(log, "开户失败", userCodes);
                return false;
            }

            LoggerUtil.info(log, "开户成功 %s %s", userCodeAndAccountType.size(), userCodes);

            // log data_move_log open success
            List<DataMoveLog> openAccountLogs = syncUserAccounts.parallelStream().map(user -> {
                DataMoveLog dataMoveLog = new DataMoveLog();
                dataMoveLog.setUserCode(user.getUserCode());
                dataMoveLog.setAccountId(userCodeAndAccountType.get(user.getUserCode()));
                dataMoveLog.setAccountType(user.getAccountType().getCode());
                dataMoveLog.setAmount(user.getAccountAmount());
                dataMoveLog.setTransferTime(new Date());
                dataMoveLog.setOpenAccountTime(new Date());
                return dataMoveLog;
            }).collect(Collectors.toList());
            dataMoveLoggerMapper.logOpenAccount(openAccountLogs);

            // 关键动作2：转账
            // 扣减机构的钱
            BigDecimal totalAmount = syncUserAccounts.stream().map(SyncUserAccount::getAccountAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            String orgAccountId = getOrgSourceAccountId(accountType, meeruuScoreAccount, meeruuShowValueAccount);
            // 扣减机构账户余额
            int result = orgAccountMapper.transferOut(orgAccountId, totalAmount);
            LoggerUtil.info(log, "syncUserAccount invoked log");
            return result == 1;
        });

    }

    public void dealAccount(DealAccountRequest request) {

        BaseAccount baseAccount = transactionTemplate.execute(status -> {
            Map<String, BaseAccount> accountId2Account = fundsBalanceBiz.getAccountBySortForUpdate(AccountSortVo.getInstance(request.getAccountId(), request.getAccountGroup()));
            BaseAccount account = accountId2Account.get(request.getAccountId());
            AccountOprType accountOprType = request.getAccountOprType();

            // 状态更新
            updateStatus(accountOprType, account, request.getAccountGroup());
            return account;
        });

        // 缓存清理
        accountCacheBiz.cleanCache(request.getAccountId(), request.getAccountGroup(), AccountType.getTypeByCode(baseAccount.getAccountType()));

    }


    public void accountCancellation(LogoutAccountRequest request) {

        cancellation(request);
    }


    private String getOrgSourceAccountId(AccountType accountType, String scoreAccountId, String showValueAccountId) {
        String result = null;
        switch (accountType) {
            case SHOW_SCORE:
                result = scoreAccountId;
                break;
            case SHOW_VALUE:
                result = showValueAccountId;
                break;
            case BALANCE:
            case DEPOSIT:
                LoggerUtil.error(log, "不支持的账户类型", accountType);
                throw new AccountException(ErrorCode.BIZ_ACCOUNT_TYPE_NOT_SUPPORT);
        }
        return result;
    }


    /**
     * 用户账户开户
     */
    private UserAccountDO openAccountForUser(UserOpenAccountRequest request) {
        Integer accountStatus = Optional.ofNullable(request.getAccountStatus()).orElse(AccountStatus.NORMAL.getCode());
        UserOpenAccountRequest userRequest = request;
        UserAccountDO userAccount = UserAccountDO.builder()
                .accountId(meeruuDidService.next18Id("U"))
                .accountName(request.getAccountName())
                .accountType(userRequest.getAccountType().getCode())
                .userCode(userRequest.getUserCode())
                .describe(userRequest.getDescribe())
                .accountOpenDate(userRequest.getAccountOpenDate())
                .status(accountStatus)
                .availableBalance(BigDecimal.ZERO)
                .frozenBalance(BigDecimal.ZERO)
                .totalBalance(BigDecimal.ZERO)
                .build();
        userAccountMapper.insert(userAccount);
        return userAccount;
    }

    /**
     * 商家账户开户
     */
    private MerchantAccountDO openAccountForMerchant(MerchantOpenAccountRequest request) {
        MerchantOpenAccountRequest merchantRequest = request;
        MerchantAccountDO merchantAccount = MerchantAccountDO.builder()
                .accountId(meeruuDidService.next18Id(merchantRequest.getAccountUsage().getPrefix()))
                .merchantCode(request.getMerchantCode())
                .merchantName(request.getMerchantName())
                .accountName(merchantRequest.getAccountName())
                .accountType(merchantRequest.getAccountType().getCode())
                .accountUsage(merchantRequest.getAccountUsage().getCode())
                .describe(merchantRequest.getDescribe())
                .accountOpenDate(merchantRequest.getAccountOpenDate())
                .status(AccountStatus.NORMAL.getCode())
                .availableBalance(BigDecimal.ZERO)
                .frozenBalance(BigDecimal.ZERO)
                .totalBalance(BigDecimal.ZERO)
                .build();
        merchantAccountMapper.insert(merchantAccount);
        return merchantAccount;
    }

    /**
     * 机构账户开户
     */
    private OrgAccountDO openAccountForOrg(OrgOpenAccountRequest request) {
        OrgAccountDO orgAccount = OrgAccountDO.builder()
                .accountId(meeruuDidService.next18Id(request.getAccountUsage().getPrefix()))
                .accountName(request.getAccountName())
                .accountType(request.getAccountType().getCode())
                .accountUsage(request.getAccountUsage().getCode())
                .describe(request.getDescribe())
                .accountOpenDate(request.getAccountOpenDate())
                .status(AccountStatus.NORMAL.getCode())
                .availableBalance(BigDecimal.ZERO)
                .frozenBalance(BigDecimal.ZERO)
                .totalBalance(BigDecimal.ZERO)
                .build();
        orgAccountMapper.insert(orgAccount);
        return orgAccount;
    }


    /**
     * 账户状态更新
     */
    private void updateStatus(AccountOprType accountOprType, BaseAccount account, AccountGroup accountGroup) {
        // 账户status检查
        this.checkChangeStatus(account.getStatus(), accountOprType);
        AccountStatus status = this.getStatusByOprType(accountOprType);

        // 执行status更新
        switch (accountGroup) {
            case USER:

                userAccountMapper.updateStatus(status.getCode(), account.getAccountId());
                break;
            case MERCHANT:

                merchantAccountMapper.updateStatus(status.getCode(), account.getAccountId());
                break;
            case ORG:

                orgAccountMapper.updateStatus(status.getCode(), account.getAccountId());
                break;
        }
    }


    private void cancellation(LogoutAccountRequest request) {
        AccountGroup accountGroup = request.getAccountGroup();
        if (request.getAccountStatus() == null) {
            request.setAccountStatus(AccountStatus.FROZEN.getCode());
        }
        switch (accountGroup) {
            case USER:
                UserLogoutAccountRequest userRequest = (UserLogoutAccountRequest) request;
                userAccountMapper.updateStatusByUserCode(userRequest.getAccountStatus(), userRequest.getUserCode(), userRequest.getDescribe());
                accountCacheBiz.cleanUserCache(userRequest.getUserCode());
                break;
            case MERCHANT:
                OrgLogoutAccountRequest orgRequest = (OrgLogoutAccountRequest) request;
                merchantAccountMapper.updateStatus(orgRequest.getAccountStatus(), orgRequest.getAccountId());
                break;
            case ORG:
                MerchantLogoutAccountRequest merchantRequest = (MerchantLogoutAccountRequest) request;
                orgAccountMapper.updateStatusByMerchantCode(merchantRequest.getAccountStatus(), merchantRequest.getMerchantCode(), merchantRequest.getDescribe());
                break;
        }
    }


    /**
     * 状态操作检查
     * 账户冻结 -> 正常
     * 账户解冻 -> 冻结
     * 账户拉黑 -> 正常、冻结
     * 账户撤销 -> 拉黑
     */
    private void checkChangeStatus(Integer status, AccountOprType accountOprType) {
        switch (accountOprType) {
            case ACCOUNT_FROZEN:
                if (!status.equals(AccountStatus.NORMAL.getCode())) {
                    LoggerUtil.error(log, "用户状态冻结异常, 用户不为正常状态");
                    throw new AccountException(ErrorCode.BIZ_OPR_STATUS_ERROR);
                }
                break;
            case ACCOUNT_THAW:
                if (!status.equals(AccountStatus.FROZEN.getCode())) {
                    LoggerUtil.error(log, "用户状态解冻异常, 用户不为冻结状态");
                    throw new AccountException(ErrorCode.BIZ_OPR_STATUS_ERROR);
                }
                break;

            case ACCOUNT_BLACK:
                if (status.equals(AccountStatus.BLACK.getCode())) {
                    LoggerUtil.error(log, "用户状态拉黑异常, 用户已经是拉黑状态了");
                    throw new AccountException(ErrorCode.BIZ_OPR_STATUS_ERROR);
                }
                break;

            case ACCOUNT_CANCEL:
                if (!status.equals(AccountStatus.BLACK.getCode())) {
                    LoggerUtil.error(log, "用户状态撤销异常, 用户不为拉黑状态");
                    throw new AccountException(ErrorCode.BIZ_OPR_STATUS_ERROR);
                }
                break;
            default:
                LoggerUtil.error(log, "用户状态操作非法");
                throw new AccountException(ErrorCode.BIZ_OPR_STATUS_ERROR);

        }

    }


    /**
     * 根据状态操作类型获取操作后状态
     */
    private AccountStatus getStatusByOprType(AccountOprType accountOprType) {
        AccountStatus status = null;
        switch (accountOprType) {
            case ACCOUNT_FROZEN:
                status = AccountStatus.FROZEN;
                break;
            case ACCOUNT_BLACK:
                status = AccountStatus.BLACK;
                break;

            case ACCOUNT_THAW:
            case ACCOUNT_CANCEL:
                status = AccountStatus.NORMAL;
                break;
            default:
                LoggerUtil.error(log, "用户状态操作非法");
                throw new AccountException(ErrorCode.BIZ_OPR_STATUS_ERROR);
        }
        return status;
    }

}
